package main

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/widget"
	"github.com/flopp/go-findfont"
)

func init() {
	fontPaths := findfont.List()
	for _, path := range fontPaths {
		//楷体:simkai.ttf
		//黑体:simhei.ttf
		if strings.Contains(path, "DroidSansFallbackFull.ttf") {
			// fmt.Println(path)
			os.Setenv("FYNE_FONT", path)
			break
		}
	}
	// fmt.Println("=============")
}

func scanPort(address string) bool {
	conn, err := net.DialTimeout("tcp", address, time.Second)
	if err != nil {
		return false
	}
	conn.Close()
	return true
}

func parsePorts(s string) ([]int, error) {
	var ports []int
	portRanges := strings.Split(s, ",")
	for _, portRange := range portRanges {
		if !strings.Contains(portRange, "-") {
			if len(portRange) == 0 {
				return nil, fmt.Errorf("IP is empty")
			}
			// 如果端口范围不包含减号，则将单个端口转换为整数并添加到列表中
			port, err := strconv.Atoi(portRange)
			if err != nil || port < 1 || port > 65535 {
				return nil, fmt.Errorf("invalid single port number: %s", portRange)
			}
			ports = append(ports, port)
		} else {
			// 如果端口范围包含减号，则解析开始和结束的端口，然后将所有中间端口添加到列表中
			rangePorts := strings.Split(portRange, "-")
			if len(rangePorts) != 2 {
				return nil, fmt.Errorf("invalid port range: %s", portRange)
			}
			startPort, err1 := strconv.Atoi(rangePorts[0])
			endPort, err2 := strconv.Atoi(rangePorts[1])
			if err1 != nil || err2 != nil || startPort < 1 || startPort > 65535 || endPort < 1 || endPort > 65535 || startPort > endPort {
				return nil, fmt.Errorf("invalid port range: %s", portRange)
			}
			for i := startPort; i <= endPort; i++ {
				ports = append(ports, i)
			}
		}
	}
	return ports, nil
}

func main() {
	app := app.New()
	w := app.NewWindow("Port Scanner")
	w.Resize(fyne.NewSize(640, 480))
	w.SetFixedSize(true)
	w.CenterOnScreen()

	// Set default values for inputField and concurrencyField
	inputField := widget.NewEntry()
	inputField.SetText("127.0.0.1")
	portField := widget.NewEntry()
	portField.SetText("1-65535")
	concurrencyField := widget.NewEntry()
	concurrencyField.SetText("10000")
	resultTextArea := widget.NewMultiLineEntry()
	progress := widget.NewProgressBar()

	form := &widget.Form{
		Items: []*widget.FormItem{
			{Text: "IP", Widget: inputField, HintText: "IP address"},
			{Text: "Port", Widget: portField, HintText: "Port,example: 22,80,8000-8020"},
			{Text: "Concurrency", Widget: concurrencyField, HintText: "Concurrency number"},
		},
		CancelText: "Exit",
		OnCancel: func() {
			app.Quit()
		},
		SubmitText: "Scan",
	}

	form.OnSubmit = func() {
		form.Disable()
		// Get the IP from the input field and clear the result text area
		ip := inputField.Text
		port := portField.Text
		concurrencyStr := concurrencyField.Text
		resultTextArea.SetText("")
		progress.SetValue(0)

		// Using the ParseIP function to parse strings into IP addresses
		ipAddr := net.ParseIP(ip)
		if ipAddr == nil {
			err := fmt.Errorf("invalid IP address")
			dialog.ShowError(err, w)
			form.Enable()
			return
		}

		portList, err := parsePorts(port)
		if err != nil {
			dialog.ShowError(err, w)
			form.Enable()
			return
		}

		// Convert concurrency string to int
		concurrency, err := strconv.Atoi(concurrencyStr)
		if err != nil {
			err := fmt.Errorf("invalid number of concurrent goroutines, %v", err)
			dialog.ShowError(err, w)
			form.Enable()
			return
		}
		if concurrency <= 0 || concurrency > 100000 {
			err := fmt.Errorf("the concurrent quantity must be between 0 and 100000")
			dialog.ShowError(err, w)
			form.Enable()
			return
		}

		results := make(chan string)

		tasks := NewTasks[string](concurrency)
		tasks.Start(func(address *string) {
			if scanPort(*address) {
				results <- fmt.Sprintf("%s is open\n", *address)
			}
		})

		// Print the scanning result in the GUI
		go func() {
			for result := range results {
				resultTextArea.Append(result)
			}
			resultTextArea.Append("Scanning completed\n")
		}()

		go func() {
			for i, port := range portList {
				progress.SetValue(float64(i+1) / float64(len(portList)))
				address := fmt.Sprintf("%s:%d", ip, port)
				tasks.SendTask(&address)
			}
			tasks.Close()
			tasks.Wait()

			close(results)
			form.Enable()
		}()
	}

	content := container.NewBorder(form, progress, nil, nil, resultTextArea)
	w.SetContent(content)
	w.ShowAndRun()
	os.Unsetenv("FYNE_FONT")
}
